#include "ndk-util_jni.h"


static pid_t s_pid;

static char *SOCK_PATH = "/data/data/com.llvision.ndkdemoprocessdemo/my.sock";
static bool isRunning = 0;
static int mSocketClientId = -1;

static jstring stringFromJNI(JNIEnv *env, jobject thiz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}


static void createServerSocket(pid_t pid);

static void error(const char *msg);

static void createClientSocket(pid_t pid);

static void nativeProtect(JNIEnv *env, jobject thiz, jstring myPid) {
    const char *tmp = env->GetStringUTFChars(myPid, NULL);

    LOGI("nativeProtect %s", tmp);
    pid_t pid = fork();
    if (pid == 0) {
        //sub-process
        if (!isRunning) {
            LOGI("子进程创建Server Socket");
            isRunning = 1;
            createServerSocket(pid);
        }
    } else if (pid > 0) {
        //parent process
        LOGI("父进程创建Client Socket");
        createClientSocket(pid);
    }
    LOGW("pid = %d", pid);


}

static void createClientSocket(pid_t pid) {
    int sockfd, servlen, n;
    struct sockaddr_un serv_addr;
    char buffer[82] = {0};

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_LOCAL;
    strcpy(serv_addr.sun_path, SOCK_PATH);
    servlen = strlen(serv_addr.sun_path) +
              sizeof(serv_addr.sun_family);
    if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
        error("Creating socket");
    if (connect(sockfd, (struct sockaddr *)
            &serv_addr, servlen) < 0)
        error("Connecting");
    strcpy(buffer, "hello world");

    LOGI("client - before write buffer = %s", buffer);
    write(sockfd, buffer, strlen(buffer));
    bzero(buffer, strlen(buffer));
    n = read(sockfd, buffer, 80);
    LOGW("The return message was %s\n", buffer);
    write(1, buffer, n);
    close(sockfd);
}

/**
 *
 * 1.create socket() <br/>
 * 2.bind() <br/>
 * 3.listen() <br/>
 * 4.accept(),This call typically blocks until a client connects with the server.
 * @param pid 子进程ID
 */
static void createServerSocket(pid_t pid) {
    //http://www.linuxhowtos.org/manpages/2/socket.htm
    //http://www.linuxhowtos.org/C_C++/socket.htm
    int sockfd, newsockfd, servlen, n;
    struct sockaddr_un  serv_addr;
    char buf[80];

    if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
        error("creating socket");
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, SOCK_PATH);
    servlen = strlen(serv_addr.sun_path) +
              sizeof(serv_addr.sun_family);
    while (isRunning) {
        int bindRet = bind(sockfd, (struct sockaddr *) &serv_addr, servlen);
        if (bindRet < 0) {
            LOGE("server---binding socket error,bindRet = %d", bindRet);
            return;
        }
        listen(sockfd, 5);
        //保证宿主进程链接成功
        while (isRunning){
            //返回值，客户端的地址,阻塞函数
            newsockfd = accept(
                    sockfd, NULL, NULL);
            if(newsockfd < 0){
                if (errno == EINTR){
                    LOGI("SERVER - continue");
                    continue;
                }else{
                    LOGE("读取错误");
                    return;
                }
            }
        }
        mSocketClientId = newsockfd;
        LOGW("apk 父进程连接上了 %d",mSocketClientId);
        break;

    }

    //读取内容
    while(isRunning){
        char buff[256] = {0};
        int n = read(mSocketClientId,buff,256);
        LOGI("server->read %s",buff);
        //执行服务
        execlp("am","am","startservice");

    }

    close(newsockfd);
    close(sockfd);
}


static void error(const char *msg) {
    LOGE("%s", msg);
    exit(0);
}

//nativeProtect(String pid)
JNINativeMethod nativeMethods[] = {
        {"stringFromJNI", "()Ljava/lang/String;",  (void *) stringFromJNI},
        {"nativeProtect", "(Ljava/lang/String;)V", (void *) nativeProtect}
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGI("*********register NdkUtil*******");

    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jclass clazz = env->FindClass("com/llvision/ndkdemoprocessdemo/utils/NdkUtil");
    int ret = env->RegisterNatives(clazz, nativeMethods,
                                   sizeof(nativeMethods) / sizeof(nativeMethods[0]));

    if (ret == 0) {
        LOGI("register OK, ret = %d", ret);
        return JNI_VERSION_1_6;
    }
    return ret;

}
